﻿#include <atlbase.h>

#include <string>
#include <sstream>
#include <algorithm>

#include "ImageElement.h"
#include "Miscellaneous.h"
#include "Clipboard.h"
#include "TessOCR.h"
#pragma comment (lib,"tessdll.lib")
#pragma comment (lib,"ws2_32.lib")

using namespace std;
using namespace Gdiplus;

CImageElement::CImageElement(void)
{
}


CImageElement::~CImageElement(void)
{
}

void CImageElement::DoFill(IHTMLDocument2* document) throw(...)
{
	// Do nothing~...
}

//void CImageElement::InitElementFromXml(IXmlReader* pReader) throw(...)
//{
//	HRESULT hr;
//	XmlNodeType nodeType;
//	const WCHAR* pwszBuffer;
//
//	
//	//TODO:
//	CElement::InitElementFromXml(pReader);
//}

void CImageElement::BuildAvailableAttributes() throw()
{
	_maa[L"size"]		= L"";
	_maa[L"suppress"]	= L"true";
	_maa[L"zoom"]		= L"1.0";
	_maa[L"margin"]		= L"0";
	_maa[L"mode"]		= L"digital";
}

void CImageElement::ValidateAttributes() const throw(...)
{
	if(_maa.find(L"size")->second == L"")
		throw wstring(L"Attribute 'size' cannot be null or empty");
}

wstring CImageElement::GetText(IHTMLDocument2* document) const throw(...)
{
	auto e = this->FindElement(document);
	this->CopyImage(document, e);
	auto bmp = this->GetOriginalImage();
	auto bmp2 = this->Decorate(bmp.get());
	auto code = this->OCR(bmp2.get());
	auto code2 = this->Revise(code);

	return code2;
}

void CImageElement::CopyImage(IHTMLDocument2* document, IHTMLElement* e) const throw(...)
{
	HRESULT hr;
	CComQIPtr<IHTMLElement> spBody;
	CComQIPtr<IHTMLElement2> spBody2;
	CComDispatchDriver spDisp;
	CComQIPtr<IHTMLControlRange> spRange;
	CComQIPtr<IHTMLControlElement> spCtl;
	CComVariant vEmpty;
	VARIANT_BOOL vbReturn;
	bool suppressSecurityWarning;

	suppressSecurityWarning = (_maa.find(L"suppress")->second == L"true" ? true : false);

	hr = document->get_body(&spBody);
	spBody2 = spBody;
	hr = spBody2->createControlRange(&spDisp);
	spRange = spDisp;
	spCtl = e;
	spRange->add(spCtl);

	if(suppressSecurityWarning)
		CMiscellaneous::SuppressSecurityWarning();
	hr = spRange->execCommand(CComBSTR(L"Copy"), VARIANT_TRUE, vEmpty, &vbReturn);
	if(suppressSecurityWarning)
		CMiscellaneous::DesuppressSecurityWarning();

	if(FAILED(hr))
		throw wstring(L"CImageElement::GetOriginalImage failed");
}

unique_ptr<Bitmap> CImageElement::GetOriginalImage() const throw(...)
{
	unique_ptr<CClipboard> upcb(new CClipboard());
	return upcb->GetImage();
}

unique_ptr<Bitmap> CImageElement::Decorate(Bitmap* source) const throw(...)
{
	const double delta = _wtof(_maa.find(L"zoom")->second.c_str());
	const int margin = _wtol(_maa.find(L"margin")->second.c_str());
	const int w = source->GetWidth(), h = source->GetHeight();

	unique_ptr<Bitmap> upbmp(new Bitmap(
		(int)(w * delta + margin * 2),
		(int)(h * delta + margin * 2)));

	unique_ptr<Graphics> g(Graphics::FromImage(upbmp.get()));
	{
		//设置高质量插值法
		g->SetInterpolationMode(InterpolationModeHighQualityBicubic);

		//设置高质量,低速度呈现平滑程度
		g->SetSmoothingMode(SmoothingModeAntiAlias);

		//清空画布并以透明背景色填充
		g->Clear(Color::Transparent);

		g->DrawImage(source, margin, margin, w * delta, h * delta);
	   
		g->Save();	
	}

	return upbmp;
}

string CImageElement::OCR(Bitmap* source) const throw(...)
{
	unique_ptr<BitmapData> updata(new BitmapData());
	Rect rect(0, 0, source->GetWidth(), source->GetHeight());
	source->LockBits(&rect, ImageLockModeRead, PixelFormat32bppARGB, updata.get());

	TessOCR engine;
	string s = engine.Recognize("cmb-china", 
		NULL, true, (unsigned char*)updata->Scan0, 
		GetPixelFormatSize(updata->PixelFormat) / 8, updata->Stride,
		0, 0, updata->Width, updata->Height);
			
	source->UnlockBits(updata.get());

	return s;
}

wstring CImageElement::Revise(const string& code) const throw(...)
{
	long size = _wtol(_maa.find(L"size")->second.c_str());
	if (size > 16)
		throw wstring(L"Attribute 'size' is too large or invalid");

	wostringstream os;
	auto mode = _maa.find(L"mode");

	if(mode->second == L"digital")
		for_each(code.cbegin(), code.cend(), [&os](string::const_reference c){
			if(c >= '0' && c <= '9')
				os << (wchar_t)c;
		});
	else if(mode->second == L"alphabet")
		for_each(code.cbegin(), code.cend(), [&os](string::const_reference c){
			if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
				os << (wchar_t)c;
		});
	else if(mode->second == L"mixed")
		for_each(code.cbegin(), code.cend(), [&os](string::const_reference c){
			if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
				os << (wchar_t)c;
		});
	else if(mode->second == L"chinese")
		for_each(code.cbegin(), code.cend(), [&os](string::const_reference c){
			//TODO:
		});
	else if(mode->second == L"all")
		for_each(code.cbegin(), code.cend(), [&os](string::const_reference c){
			os << (wchar_t)c;
		});
	else
		throw wstring(L"Unknown mode ->") + mode->second;

	return os.str().substr(0, size);
}